Remove shopfloor server, which is now in factory package. CQ-DEPEND=CL:32038 BUG=chrome-os-partner:8812 TEST=cbuildbot --remote x86-alex-release Change-Id: I3bfa105f4d3d4da2bde53e27c932f7f7cfccbce0 Reviewed-on: https://gerrit.chromium.org/gerrit/31978 Reviewed-by: Jon Salz <jsalz@chromium.org> Tested-by: Jon Salz <jsalz@chromium.org> Commit-Ready: Jon Salz <jsalz@chromium.org> 
diff --git a/factory_setup/README.txt b/factory_setup/README.txt index fcbabef..5d51fb1 100644 --- a/factory_setup/README.txt +++ b/factory_setup/README.txt 
@@ -10,3 +10,5 @@  So all scripts must use only the libraries in same folder and not relying on any  files in cros source tree (except chromeos-common.sh).   +Shopfloor scripts have been moved to ../shopfloor in the factory bundle; +source code is located in the platform/factory repository. 
diff --git a/factory_setup/factory_update_server.py b/factory_setup/factory_update_server.py deleted file mode 100755 index 8874279..0000000 --- a/factory_setup/factory_update_server.py +++ /dev/null 
@@ -1,244 +0,0 @@ -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -'''Factory Update Server. - -The factory update server is implemented as a thread to be started by -shop floor server. It monitors the given state_dir and detects -factory.tar.bz2 file changes and then sets up the new update files -into factory_dir (under state_dir). It also starts an rsync server -to serve factory_dir for clients to fetch update files. -''' - -import errno -import logging -import os -import shutil -import subprocess -import threading -import time - - -FACTORY_DIR = 'factory' -AUTOTEST_DIR = 'autotest' -TARBALL_NAME = 'factory.tar.bz2' -LATEST_SYMLINK = 'latest' -LATEST_MD5SUM = 'latest.md5sum' -MD5SUM = 'MD5SUM' -DEFAULT_RSYNCD_PORT = 8083 -RSYNCD_CONFIG_TEMPLATE = '''port = %(port)d -pid file = %(pidfile)s -log file = %(logfile)s -use chroot = no -[factory] - path = %(factory_dir)s - read only = yes -''' - - -def StartRsyncServer(port, state_dir, factory_dir): - configfile = os.path.join(state_dir, 'rsyncd.conf') - pidfile = os.path.join(state_dir, 'rsyncd.pid') - if os.path.exists(pidfile): - # Since rsyncd will not overwrite it if it already exists - os.unlink(pidfile) - logfile = os.path.join(state_dir, 'rsyncd.log') - data = RSYNCD_CONFIG_TEMPLATE % dict(port=port, - pidfile=pidfile, - logfile=logfile, - factory_dir=factory_dir) - with open(configfile, 'w') as f: - f.write(data) - - p = subprocess.Popen(('rsync', '--daemon', '--no-detach', - '--config=%s' % configfile)) - logging.info('Rsync server (pid %d) started on port %d', p.pid, port) - return p - - -def StopRsyncServer(rsyncd_process): - logging.info('Stopping rsync server (pid %d)', rsyncd_process.pid) - rsyncd_process.terminate() - - # If not terminated in a second, send a kill -9. - def WaitAndKill(): - time.sleep(1) - try: - rsyncd_process.kill() - except: - pass - thread = threading.Thread(target=WaitAndKill) - thread.daemon = True - thread.start() - - rsyncd_process.wait() - logging.debug('Rsync server stopped') - - -def CalculateMd5sum(filename): - p = subprocess.Popen(('md5sum', filename), stdout=subprocess.PIPE) - output, _ = p.communicate() - return output.split()[0] - - -class FactoryUpdateServer(): - - def __init__(self, state_dir, rsyncd_port=DEFAULT_RSYNCD_PORT, - poll_interval_sec=1): - self.state_dir = state_dir - self.factory_dir = os.path.join(state_dir, FACTORY_DIR) - self.rsyncd_port = rsyncd_port - if not os.path.exists(self.factory_dir): - os.mkdir(self.factory_dir) - self.poll_interval_sec = poll_interval_sec - self._stop_event = threading.Event() - self._rsyncd = StartRsyncServer(rsyncd_port, state_dir, self.factory_dir) - self._tarball_path = os.path.join(self.state_dir, TARBALL_NAME) - - self._thread = None - self._last_stat = None - self._run_count = 0 - self._update_count = 0 - self._errors = 0 - - def Start(self): - assert not self._thread - self._thread = threading.Thread(target=self.Run) - self._thread.start() - - def Stop(self): - if self._rsyncd: - StopRsyncServer(self._rsyncd) - self._rsyncd = None - - self._stop_event.set() - - if self._thread: - self._thread.join() - self._thread = None - - def _HandleTarball(self): - new_tarball_path = self._tarball_path + '.new' - - # Copy the tarball to avoid possible race condition. - shutil.copyfile(self._tarball_path, new_tarball_path) - - # Calculate MD5. - md5sum = CalculateMd5sum(new_tarball_path) - logging.info('Processing tarball ' + self._tarball_path + ' (md5sum=%s)', - md5sum) - - # Move to a file containing the MD5. - final_tarball_path = self._tarball_path + '.' + md5sum - os.rename(new_tarball_path, final_tarball_path) - - # Create subfolder to hold tarball contents. - final_subfolder = os.path.join(self.factory_dir, md5sum) - final_md5sum = os.path.join(final_subfolder, FACTORY_DIR, MD5SUM) - if os.path.exists(final_subfolder): - if not (os.path.exists(final_md5sum) and - open(final_md5sum).read().strip() == md5sum): - logging.warn('Update directory %s appears not to be set up properly ' - '(missing or bad MD5SUM); delete it and restart update ' - 'server?', final_subfolder) - return - logging.info('Update is already deployed into %s', final_subfolder) - else: - new_subfolder = final_subfolder + '.new' - if os.path.exists(new_subfolder): - shutil.rmtree(new_subfolder) - os.mkdir(new_subfolder) - - # Extract tarball. - success = False - try: - try: - logging.info('Staging into %s', new_subfolder) - subprocess.check_call(('tar', '-xjf', final_tarball_path, - '-C', new_subfolder)) - except subprocess.CalledProcessError: - logging.error('Failed to extract update files to subfolder %s', - new_subfolder) - return - - missing_dirs = [ - d for d in (FACTORY_DIR, AUTOTEST_DIR) - if not os.path.exists(os.path.join(new_subfolder, d))] - if missing_dirs: - logging.error('Tarball is missing directories: %r', missing_dirs) - return - - factory_dir = os.path.join(new_subfolder, FACTORY_DIR) - with open(os.path.join(factory_dir, MD5SUM), 'w') as f: - f.write(md5sum) - - # Extracted and verified. Move it in place. - os.rename(new_subfolder, final_subfolder) - logging.info('Moved to final directory %s', final_subfolder) - - success = True - self._update_count += 1 - finally: - if os.path.exists(new_subfolder): - shutil.rmtree(new_subfolder, ignore_errors=True) - if (not success) and os.path.exists(final_subfolder): - shutil.rmtree(final_subfolder, ignore_errors=True) - - # Update symlink and latest.md5sum. - linkname = os.path.join(self.factory_dir, LATEST_SYMLINK) - if os.path.islink(linkname): - os.remove(linkname) - os.symlink(md5sum, linkname) - with open(os.path.join(self.factory_dir, LATEST_MD5SUM), 'w') as f: - f.write(md5sum) - logging.info('Update files (%s) setup complete', md5sum) - - def Run(self): - while True: - try: - self.RunOnce() - except: - logging.exception('Error in event loop') - - self._stop_event.wait(self.poll_interval_sec) - if self._stop_event.is_set(): - break - - def RunOnce(self): - try: - self._run_count += 1 - - try: - stat = os.stat(self._tarball_path) - except OSError as e: - if e.errno == errno.ENOENT: - # File doesn't exist - return - raise - - if (self._last_stat and - ((stat.st_mtime, stat.st_size) == - (self._last_stat.st_mtime, self._last_stat.st_size))): - # No change - return - - self._last_stat = stat - try: - with open(os.devnull, "w") as devnull: - logging.info('Verifying integrity of tarball %s', self._tarball_path) - subprocess.check_call(['tar', '-tjf', self._tarball_path], - stdout=devnull, stderr=devnull) - except subprocess.CalledProcessError: - # E.g., still copying - logging.warn('Tarball %s (%d bytes) is corrupt or incomplete', - self._tarball_path, stat.st_size) - return - - # Re-stat in case it finished being written while we were - # verifying it. - self._last_stat = os.stat(self._tarball_path) - self._HandleTarball() - except: - self._errors += 1 - raise 
diff --git a/factory_setup/factory_update_server_unittest.py b/factory_setup/factory_update_server_unittest.py deleted file mode 100755 index dcb1cae..0000000 --- a/factory_setup/factory_update_server_unittest.py +++ /dev/null 
@@ -1,94 +0,0 @@ -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -'''Tests for Factory Update Server.''' - -import factory_update_server -import os -import shutil -import sys -import tempfile -import time -import unittest - - -BASE_DIR = os.path.dirname(os.path.realpath(__file__)) - - -class BasicTests(unittest.TestCase): - def testMd5sumCalculation(self): - md5sum = factory_update_server.CalculateMd5sum( - os.path.join(BASE_DIR, 'testdata/shopfloor/factory.tar.bz2')) - self.assertEqual(md5sum, '18cac06201e65e060f757193c153cacb') - - -class FactoryUpdateServerTest(unittest.TestCase): - def setUp(self): - self.work_dir = tempfile.mkdtemp(prefix='dts') - self._CreateUpdateServer() - - def _CreateUpdateServer(self): - self.update_server = factory_update_server.FactoryUpdateServer( - self.work_dir, poll_interval_sec=0.1) - - def tearDown(self): - self.update_server.Stop() - self.assertEqual(0, self.update_server._errors) - shutil.rmtree(self.work_dir) - - def testThread(self): - # Start the thread (make sure it starts/stops properly). - self.update_server.Start() - self.update_server.Stop() - self.assertTrue(self.update_server._run_count) - - def testLogic(self): - self.update_server.RunOnce() - - self.assertTrue(os.path.isdir(os.path.join(self.work_dir, 'factory'))) - self.assertTrue(self.update_server._rsyncd.poll() is None) - - # No latest.md5sum file at the beginning. - md5file = os.path.join(self.work_dir, 'factory/latest.md5sum') - self.assertFalse(os.path.exists(md5file)) - self.assertEqual(0, self.update_server._update_count) - - tarball_src = os.path.join(BASE_DIR, 'testdata/shopfloor/factory.tar.bz2') - tarball_dest = os.path.join(self.work_dir, 'factory.tar.bz2') - - # Put partially-written factory.tar.bz2 into the working folder. - with open(tarball_dest, "w") as f: - print >>f, "Not really a bzip2" - self.update_server.RunOnce() - - # Put factory.tar.bz2 into the working folder. - shutil.copy(tarball_src, tarball_dest) - # Kick the update server - self.update_server.RunOnce() - - # Check that latest.md5sum is created with correct value and update files - # extracted. - self.assertTrue(os.path.isfile(md5file), md5file) - with open(md5file, 'r') as f: - self.assertEqual('18cac06201e65e060f757193c153cacb', f.read().strip()) - self.assertTrue(os.path.isdir(os.path.join( - self.work_dir, 'factory/18cac06201e65e060f757193c153cacb'))) - self.assertEqual(1, self.update_server._update_count) - - # Kick the update server again. Nothing should happen. - self.update_server.RunOnce() - self.assertEqual(1, self.update_server._update_count) - - # Stop the update server and set up a new one. The md5sum file - # should be recreated. - self.update_server.Stop() - del self.update_server - os.unlink(md5file) - self._CreateUpdateServer() - self.update_server.RunOnce() - with open(md5file, 'r') as f: - self.assertEqual('18cac06201e65e060f757193c153cacb', f.read().strip()) - -if __name__ == '__main__': - unittest.main() 
diff --git a/factory_setup/shopfloor/__init__.py b/factory_setup/shopfloor/__init__.py deleted file mode 100644 index aa4a767..0000000 --- a/factory_setup/shopfloor/__init__.py +++ /dev/null 
@@ -1,276 +0,0 @@ -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - - -"""The package initialization and abstract base class for shop floor systems. - -Every implementations should inherit ShopFloorBase and override the member -functions to interact with their real shop floor system. -""" - -import csv -import glob -import logging -import os -import time -import xmlrpclib - -# In current implementation, we use xmlrpclib.Binary to prepare blobs. -from xmlrpclib import Binary - -import factory_update_server - - -EVENTS_DIR = 'events' -REPORTS_DIR = 'reports' -UPDATE_DIR = 'update' -HWID_UPDATER_PATTERN = 'hwid_*' -REGISTRATION_CODE_LOG_CSV = 'registration_code_log.csv' - - -class ShopFloorException(Exception): - pass - - -class ShopFloorBase(object): - """Base class for shopfloor servers. - - Properties: - config: The configuration data provided by the '-c' argument to - shopfloor_server. - data_dir: The top-level directory for shopfloor data. - """ - - NAME = 'ShopFloorBase' - VERSION = 4 - - def _InitBase(self): - """Initializes the base class.""" - if not os.path.exists(self.data_dir): - logging.warn('Data directory %s does not exist; creating it', - self.data_dir) - os.makedirs(self.data_dir) - - self._registration_code_log = open( - os.path.join(self.data_dir, REGISTRATION_CODE_LOG_CSV), "ab", 0) - class Dialect(csv.excel): - lineterminator = '\n' - self._registration_code_writer = csv.writer(self._registration_code_log, - dialect=Dialect) - - # Put events uploaded from DUT in the "events" directory in data_dir. - self._events_dir = os.path.join(self.data_dir, EVENTS_DIR) - if not os.path.isdir(self._events_dir): - os.mkdir(self._events_dir) - - # Dynamic test directory for holding updates is called "update" in data_dir. - update_dir = os.path.join(self.data_dir, UPDATE_DIR) - if os.path.exists(update_dir): - self.update_dir = os.path.realpath(update_dir) - self.update_server = factory_update_server.FactoryUpdateServer( - self.update_dir) - else: - logging.warn('Update directory %s does not exist; ' - 'disabling update server.', update_dir) - self.update_dir = None - self.update_server = None - - def _StartBase(self): - """Starts the base class.""" - if self.update_server: - logging.debug('Starting factory update server...') - self.update_server.Start() - - def _StopBase(self): - """Stops the base class.""" - if self.update_server: - self.update_server.Stop() - - def Init(self): - """Initializes the shop floor system. - - Subclasses should implement this rather than __init__. - """ - pass - - def Ping(self): - """Always returns true (for client to check if server is working).""" - return True - - def GetHWID(self, serial): - """Returns appropriate HWID according to given serial number. - - Args: - serial: A string of device serial number. - - Returns: - The associated HWID string. - - Raises: - ValueError if serial is invalid, or other exceptions defined by individual - modules. Note this will be converted to xmlrpclib.Fault when being used as - a XML-RPC server module. - """ - raise NotImplementedError('GetHWID') - - def _GetHWIDUpdaterPath(self): - """Returns the path to HWID updater bundle, if available. - - Returns: - The path to the file (or None). - - Raises: - ShopFloorException if there are >1 HWID bundles available. - """ - bundles = glob.glob(os.path.join(self.data_dir, HWID_UPDATER_PATTERN)) - if not bundles: - return None - - if len(bundles) > 1: - raise ShopFloorException('Multiple HWID bundles available: %s (please ' - 'delete all but one)' % bundles) - - return bundles[0] - - def GetHWIDUpdater(self): - """Returns a HWID updater bundle, if available. - - Returns: - The binary-encoded contents of a file named 'hwid_*' in the data - directory. If there are no such files, returns None. - - Raises: - ShopFloorException if there are >1 HWID bundles available. - """ - path = self._GetHWIDUpdaterPath() - return open(path).read() if path else None - - def GetVPD(self, serial): - """Returns VPD data to set (in dictionary format). - - Args: - serial: A string of device serial number. - - Returns: - VPD data in dict {'ro': dict(), 'rw': dict()} - - Raises: - ValueError if serial is invalid, or other exceptions defined by individual - modules. Note this will be converted to xmlrpclib.Fault when being used as - a XML-RPC server module. - """ - raise NotImplementedError('GetVPD') - - def UploadReport(self, serial, report_blob, report_name=None): - """Uploads a report file. - - Args: - serial: A string of device serial number. - report_blob: Blob of compressed report to be stored (must be prepared by - shopfloor.Binary) - report_name: (Optional) Suggested report file name. This is uslally - assigned by factory test client programs (ex, gooftool); however - server implementations still may use other names to store the report. - - Returns: - True on success. - - Raises: - ValueError if serial is invalid, or other exceptions defined by individual - modules. Note this will be converted to xmlrpclib.Fault when being used as - a XML-RPC server module. - """ - raise NotImplementedError('UploadReport') - - def Finalize(self, serial): - """Marks target device (by serial) to be ready for shipment. - - Args: - serial: A string of device serial number. - - Returns: - True on success. - - Raises: - ValueError if serial is invalid, or other exceptions defined by individual - modules. Note this will be converted to xmlrpclib.Fault when being used as - a XML-RPC server module. - """ - raise NotImplementedError('Finalize') - - def GetRegistrationCodeMap(self, serial): - """Returns the registration code map for the given serial number. - - Returns: - {'user': registration_code, 'group': group_code} - - Raises: - ValueError if serial is invalid, or other exceptions defined by individual - modules. Note this will be converted to xmlrpclib.Fault when being used as - a XML-RPC server module. - """ - raise NotImplementedError('GetRegistrationCode') - - def LogRegistrationCodeMap(self, hwid, registration_code_map): - """Logs that a particular registration code has been used.""" - self._registration_code_writer.writerow( - [hwid, registration_code_map['user'], registration_code_map['group'], - time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())]) - os.fdatasync(self._registration_code_log.fileno()) - - def GetTestMd5sum(self): - """Gets the latest md5sum of dynamic test tarball. - - Returns: - A string of md5sum. None if no dynamic test tarball is installed. - """ - if not self.update_dir: - return None - - md5file = os.path.join(self.update_dir, - factory_update_server.FACTORY_DIR, - factory_update_server.LATEST_MD5SUM) - if not os.path.isfile(md5file): - return None - with open(md5file, 'r') as f: - return f.readline().strip() - - def GetUpdatePort(self): - """Returns the port to use for rsync updates. - - Returns: - The port, or None if there is no update server available. - """ - return self.update_server.rsyncd_port if self.update_server else None - - def UploadEvent(self, log_name, chunk): - """Uploads a chunk of events. - - Args: - log_name: A string of the event log filename. Event logging module creates - event files with an unique identifier (uuid) as part of the filename. - chunk: A string containing one or more events. Events are in YAML format - and separated by a "---" as specified by YAML. A chunk contains one or - more events with separator. - - Returns: - True on success. - - Raises: - IOError if unable to save the chunk of events. - """ - if not os.path.exists(self._events_dir): - os.makedirs(self._events_dir) - - if isinstance(chunk, Binary): - chunk = chunk.data - - log_file = os.path.join(self._events_dir, log_name) - with open(log_file, 'a') as f: - f.write(chunk) - return True - - def GetTime(self): - """Returns the current time in seconds since the epoch.""" - return time.time() 
diff --git a/factory_setup/shopfloor/simple.py b/factory_setup/shopfloor/simple.py deleted file mode 100644 index e36114b..0000000 --- a/factory_setup/shopfloor/simple.py +++ /dev/null 
@@ -1,185 +0,0 @@ -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -"""ChromeOS factory shop floor system implementation, using CSV input. - -This module provides an easy way to setup and use shop floor system. Use Google -Docs or Excel to create a spreadsheet and export as CSV (comma separated -values), with following fields: - - serial_number: The serial number of each device. - hwid: The HWID string assigned for each serial number. - ro_vpd_*: Read-only VPD values. Example: ro_vpd_test_data will be converted to - "test_data" in RO_VPD section. - rw_vpd_*: Read-writeable VPD values, using same syntax described in ro_vpd_*. - -To use this module, run following command in factory_setup folder: - shopfloor_server.py -m shopfloor.simple.ShopFloor -c PATH_TO_CSV_FILE.csv - -You can find a sample CSV file in in: - factory_setup/test_data/shopfloor/simple.csv -""" - -import csv -import logging -import os -import re -import time - -from xmlrpclib import Binary - -import shopfloor - - -class ShopFloor(shopfloor.ShopFloorBase): - """Sample shop floor system, using CSV file as input. - - Device data is read from a 'devices.csv' file in the data directory. - """ - NAME = "CSV-file based shop floor system" - VERSION = 4 - - def Init(self): - devices_csv = os.path.join(self.data_dir, 'devices.csv') - logging.info("Parsing %s...", devices_csv) - self.data_store = LoadCsvData(devices_csv) - logging.info("Loaded %d entries from %s.", - len(self.data_store), devices_csv) - - # Put uploaded reports in a "reports" folder inside data_dir. - self.reports_dir = os.path.join(self.data_dir, 'reports') - if not os.path.isdir(self.reports_dir): - os.mkdir(self.reports_dir) - - # Try to touch some files inside directory, to make sure the directory is - # writable, and everything I/O system is working fine. - stamp_file = os.path.join(self.reports_dir, ".touch") - with open(stamp_file, "w") as stamp_handle: - stamp_handle.write("%s - VERSION %s" % (self.NAME, self.VERSION)) - os.remove(stamp_file) - - def _CheckSerialNumber(self, serial): - """Checks if serial number is valid, otherwise raise ValueError.""" - if serial in self.data_store: - return True - message = "Unknown serial number: %s" % serial - logging.error(message) - raise ValueError(message) - - def GetHWID(self, serial): - self._CheckSerialNumber(serial) - return self.data_store[serial]['hwid'] - - def GetVPD(self, serial): - self._CheckSerialNumber(serial) - return self.data_store[serial]['vpd'] - - def GetRegistrationCodeMap(self, serial): - self._CheckSerialNumber(serial) - registration_code_map = self.data_store[serial]['registration_code_map'] - self.LogRegistrationCodeMap(self.data_store[serial]['hwid'], - registration_code_map) - return registration_code_map - - def UploadReport(self, serial, report_blob, report_name=None): - def is_gzip_blob(blob): - """Check (not 100% accurate) if input blob is gzipped.""" - GZIP_MAGIC = '\x1f\x8b' - return blob[:len(GZIP_MAGIC)] == GZIP_MAGIC - - self._CheckSerialNumber(serial) - if isinstance(report_blob, shopfloor.Binary): - report_blob = report_blob.data - if not report_name: - report_name = ('%s-%s.rpt' % (re.sub('[^a-zA-Z0-9]', '', serial), - time.strftime("%Y%m%d-%H%M%S%z"))) - if is_gzip_blob(report_blob): - report_name += ".gz" - report_path = os.path.join(self.reports_dir, report_name) - with open(report_path, "wb") as report_obj: - report_obj.write(report_blob) - - def Finalize(self, serial): - # Finalize is currently not implemented. - self._CheckSerialNumber(serial) - logging.info("Finalized: %s", serial) - - -def LoadCsvData(filename): - """Loads a CSV file and returns structured shop floor system data.""" - # Required fields. - KEY_SERIAL_NUMBER = 'serial_number' - KEY_HWID = 'hwid' - KEY_REGISTRATION_CODE_USER = 'registration_code_user' - KEY_REGISTRATION_CODE_GROUP = 'registration_code_group' - - # Optional fields. - PREFIX_RO_VPD = 'ro_vpd_' - PREFIX_RW_VPD = 'rw_vpd_' - VPD_PREFIXES = (PREFIX_RO_VPD, PREFIX_RW_VPD) - - REQUIRED_KEYS = (KEY_SERIAL_NUMBER, KEY_HWID) - OPTIONAL_KEYS = (KEY_REGISTRATION_CODE_USER, KEY_REGISTRATION_CODE_GROUP) - OPTIONAL_PREFIXES = VPD_PREFIXES - - def check_field_name(name): - """Checks if argument is an valid input name.""" - if name in REQUIRED_KEYS or name in OPTIONAL_KEYS: - return True - for prefix in OPTIONAL_PREFIXES: - if name.startswith(prefix): - return True - return False - - def build_vpd(source): - """Builds VPD structure by input source.""" - vpd = {'ro': {}, 'rw': {}} - for key, value in source.items(): - for prefix in VPD_PREFIXES: - if not key.startswith(prefix): - continue - # Key format: $type_vpd_$name (ex, ro_vpd_serial_number) - (key_type, _, key_name) = key.split('_', 2) - if value is None: - continue - vpd[key_type][key_name.strip()] = value.strip() - return vpd - - def build_registration_code_map(source): - """Builds registration_code_map structure. - - Returns: - A dict containing 'user' and 'group' keys. - """ - return {'user': source.get(KEY_REGISTRATION_CODE_USER), - 'group': source.get(KEY_REGISTRATION_CODE_GROUP)} - - data = {} - with open(filename, 'rb') as source: - reader = csv.DictReader(source) - row_number = 0 - for row in reader: - row_number += 1 - if KEY_SERIAL_NUMBER not in row: - raise ValueError("Missing %s in row %d" % (KEY_SERIAL_NUMBER, - row_number)) - serial_number = row[KEY_SERIAL_NUMBER].strip() - hwid = row[KEY_HWID].strip() - - # Checks data validity. - if serial_number in data: - raise ValueError("Duplicated %s in row %d: %s" % - (KEY_SERIAL_NUMBER, row_number, serial_number)) - if None in row: - raise ValueError("Extra fields in row %d: %s" % - (row_number, ','.join(row[None]))) - for field in row: - if not check_field_name(field): - raise ValueError("Invalid field: %s" % field) - - entry = {'hwid': hwid, - 'vpd': build_vpd(row), - 'registration_code_map': build_registration_code_map(row)} - data[serial_number] = entry - return data 
diff --git a/factory_setup/shopfloor/template.py b/factory_setup/shopfloor/template.py deleted file mode 100644 index 11c57cf..0000000 --- a/factory_setup/shopfloor/template.py +++ /dev/null 
@@ -1,50 +0,0 @@ -# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - - -""" -(CHANGE THIS) This is a template for creating new implementation of factory shop -floor system module. -""" - - -# Add required python modules here. -import logging - -# Always include 'shopfloor' for the abstract base class. -import shopfloor - - -class ShopFloor(shopfloor.ShopFloorBase): - """(CHANGE THIS) Implementation for factory shop floor system.""" - NAME = '(CHANGE THIS) Shopfloor system template' - VERSION = 1 - - def __init__(self, config=None): - """See help(ShopFloorBase.__init__)""" - logging.info('Shop floor system started.') - - def GetHWID(self, serial): - """See help(ShopFloorBase.GetHWID)""" - raise NotImplementedError('GetHWID') - - def GetVPD(self, serial): - """See help(ShopFloorBase.GetVPD)""" - raise NotImplementedError('GetVPD') - - def UploadReport(self, serial, report_blob, report_name=None): - """See help(ShopFloorBase.UploadReport)""" - raise NotImplementedError('UploadReport') - - def Finalize(self, serial): - """See help(ShopFloorBase.Finalize)""" - raise NotImplementedError('Finalize') - - def GetTestMd5sum(self): - """See help(ShopFloorBase.GetTestMd5sum)""" - raise NotImplementedError('GetTestMd5sum') - - def UploadEvent(self, log_name, chunk): - """See help(ShopFloorBase.UploadEvent)""" - raise NotImplementedError('UploadEvent') 
diff --git a/factory_setup/shopfloor_server b/factory_setup/shopfloor_server deleted file mode 120000 index 6cc4b35..0000000 --- a/factory_setup/shopfloor_server +++ /dev/null 
@@ -1 +0,0 @@ -shopfloor_server.py \ No newline at end of file 
diff --git a/factory_setup/shopfloor_server.py b/factory_setup/shopfloor_server.py deleted file mode 100755 index 091ef78..0000000 --- a/factory_setup/shopfloor_server.py +++ /dev/null 
@@ -1,160 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - - -''' -This file starts a server for factory shop floor system. - -To use it, invoke as a standalone program and assign the shop floor system -module you want to use (modules are located in "shopfloor" subdirectory). - -Example: - ./shopfloor_server -m shopfloor.simple.ShopFloor -''' - - -import hashlib -import imp -import logging -import optparse -import os -import shopfloor -import socket -import SimpleXMLRPCServer -from subprocess import Popen, PIPE - - -_DEFAULT_SERVER_PORT = 8082 -# By default, this server is supposed to serve on same host running omaha -# server, accepting connections from client devices; so the address to bind is -# "all interfaces (0.0.0.0)". For partners running server on clients, they may -# want to change address to "localhost". -_DEFAULT_SERVER_ADDRESS = '0.0.0.0' - - -def _LoadShopFloorModule(name): - '''Loads a specified python module. - - Args: - name: Name of target module, in PACKAGE.MODULE.CLASS format. - - Returns: - Module reference. - ''' - (module_path, _, class_name) = name.rpartition('.') - logging.debug('_LoadShopFloorModule: trying %s.%s', module_path, class_name) - return getattr(__import__(module_path, fromlist=[class_name]), class_name) - - -def _RunAsServer(address, port, instance): - '''Starts a XML-RPC server in given address and port. - - Args: - address: Address to bind server. - port: Port for server to listen. - instance: Server instance for incoming XML RPC requests. - - Returns: - Never returns if the server is started successfully, otherwise some - exception will be raised. - ''' - server = SimpleXMLRPCServer.SimpleXMLRPCServer((address, port), - allow_none=True, - logRequests=False) - server.register_introspection_functions() - server.register_instance(instance) - logging.info('Server started: http://%s:%s "%s" version %s', - address, port, instance.NAME, instance.VERSION) - server.serve_forever() - - -def main(): - '''Main entry when being invoked by command line.''' - parser = optparse.OptionParser() - parser.add_option('-a', '--address', dest='address', metavar='ADDR', - default=_DEFAULT_SERVER_ADDRESS, - help='address to bind (default: %default)') - parser.add_option('-p', '--port', dest='port', metavar='PORT', type='int', - default=_DEFAULT_SERVER_PORT, - help='port to bind (default: %default)') - parser.add_option('-m', '--module', dest='module', metavar='MODULE', - default='shopfloor.ShopFloorBase', - help=('shop floor system module to load, in ' - 'PACKAGE.MODULE.CLASS format. E.g.: ' - 'shopfloor.simple.ShopFloor (default: %default)')) - parser.add_option('-c', '--config', dest='config', metavar='CONFIG', - help='configuration data for shop floor system') - parser.add_option('-d', '--data-dir', dest='data_dir', metavar='DIR', - default=os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'shopfloor_data'), - help=('data directory for shop floor system ' - '(default: %default)')) - parser.add_option('-v', '--verbose', action='count', dest='verbose', - help='increase message verbosity') - parser.add_option('-q', '--quiet', action='store_true', dest='quiet', - help='turn off verbose messages') - (options, args) = parser.parse_args() - if args: - parser.error('Invalid args: %s' % ' '.join(args)) - - if not options.module: - parser.error('You need to assign the module to be loaded (-m).') - - verbosity_map = {0: logging.INFO, - 1: logging.DEBUG} - verbosity = verbosity_map.get(options.verbose or 0, logging.NOTSET) - log_format = '%(asctime)s %(levelname)s ' - if options.verbose > 0: - log_format += '(%(filename)s:%(lineno)d) ' - log_format += '%(message)s' - logging.basicConfig(level=verbosity, format=log_format) - if options.quiet: - logging.disable(logging.INFO) - - # Disable all DNS lookups, since otherwise the logging code may try to - # resolve IP addresses, which may delay request handling. - def FakeGetFQDN(name=''): - return name or 'localhost' - socket.getfqdn = FakeGetFQDN - - try: - logging.debug('Loading shop floor system module: %s', options.module) - instance = _LoadShopFloorModule(options.module)() - - if not isinstance(instance, shopfloor.ShopFloorBase): - logging.critical('Module does not inherit ShopFloorBase: %s', - options.module) - exit(1) - - instance.data_dir = options.data_dir - instance.config = options.config - instance._InitBase() - instance.Init() - except: - logging.exception('Failed loading module: %s', options.module) - exit(1) - - # Find the HWID updater (if any). Throw an exception if there are >1. - hwid_updater_path = instance._GetHWIDUpdaterPath() - if hwid_updater_path: - logging.info('Using HWID updater %s (md5sum %s)' % ( - hwid_updater_path, - hashlib.md5(open(hwid_updater_path).read()).hexdigest())) - else: - logging.warn('No HWID updater id currently available; add a single ' - 'file named %s to enable dynamic updating of HWIDs.' % - os.path.join(options.data_dir, shopfloor.HWID_UPDATER_PATTERN)) - - try: - instance._StartBase() - logging.debug('Starting RPC server...') - _RunAsServer(address=options.address, port=options.port, instance=instance) - finally: - instance._StopBase() - - -if __name__ == '__main__': - main() 
diff --git a/factory_setup/shopfloor_unittest.py b/factory_setup/shopfloor_unittest.py deleted file mode 100755 index 839df08..0000000 --- a/factory_setup/shopfloor_unittest.py +++ /dev/null 
@@ -1,204 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -"""Unit tests for shop floor server.""" - -import os -import re -import shutil -import subprocess -import sys -import tempfile -import time -import unittest -import xmlrpclib - -import shopfloor -import shopfloor_server - - -class ShopFloorServerTest(unittest.TestCase): - - def setUp(self): - '''Starts shop floor server and creates client proxy.''' - self.server_port = shopfloor_server._DEFAULT_SERVER_PORT - self.base_dir = os.path.dirname(os.path.abspath(sys.argv[0])) - self.data_dir = tempfile.mkdtemp(prefix='shopfloor_data.') - self.registration_code_log = ( - os.path.join(self.data_dir, shopfloor.REGISTRATION_CODE_LOG_CSV)) - csv_source = os.path.join(self.base_dir, 'testdata', 'shopfloor', - 'devices.csv') - csv_work = os.path.join(self.data_dir, 'devices.csv') - shutil.copyfile(csv_source, csv_work) - os.mkdir(os.path.join(self.data_dir, shopfloor.UPDATE_DIR)) - os.mkdir(os.path.join(self.data_dir, shopfloor.UPDATE_DIR, 'factory')) - - cmd = ['python', os.path.join(self.base_dir, 'shopfloor_server.py'), - '-q', '-a', 'localhost', '-p', str(self.server_port), - '-m', 'shopfloor.simple.ShopFloor', - '-d', self.data_dir] - self.process = subprocess.Popen(cmd) - self.proxy = xmlrpclib.ServerProxy('http://localhost:%s' % self.server_port, - allow_none=True) - # Waits the server to be ready, up to 1 second. - for i in xrange(10): - try: - self.proxy.Ping() - break - except: - time.sleep(0.1) - continue - else: - self.fail('Server never came up') - - def tearDown(self): - '''Terminates shop floor server''' - self.process.terminate() - self.process.wait() - shutil.rmtree(self.data_dir) - - def testGetHWID(self): - # Valid HWIDs range from CR001001 to CR001025 - for i in range(25): - serial = 'CR0010%02d' % (i + 1) - result = self.proxy.GetHWID(serial) - self.assertTrue(result.startswith('MAGICA ')) - self.assertEqual(len(result.split(' ')), 4) - - # Test invalid serial numbers - self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, '0000') - self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, 'garbage') - self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, '') - self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, None) - self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, 'CR001000') - self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWID, 'CR001026') - - def testGetHWIDUpdater_None(self): - self.assertEquals(None, self.proxy.GetHWIDUpdater()) - - def testGetHWIDUpdater_One(self): - with open(os.path.join(self.data_dir, 'hwid_updater.sh'), 'w') as f: - f.write('foobar') - self.assertEquals('foobar', self.proxy.GetHWIDUpdater().data) - - def testGetHWIDUpdater_Two(self): - for i in (1, 2): - open(os.path.join(self.data_dir, 'hwid_updater_%d.sh' % i), 'w').close() - self.assertRaises(xmlrpclib.Fault, self.proxy.GetHWIDUpdater) - - def testGetVPD(self): - # VPD fields defined in simple.csv - RO_FIELDS = ('keyboard_layout', 'initial_locale', 'initial_timezone') - RW_FIELDS_SET1 = ('wifi_mac', 'cellular_mac') - RW_FIELDS_SET2 = ('wifi_mac', ) - - vpd = self.proxy.GetVPD('CR001005') - for field in RO_FIELDS: - self.assertTrue(field in vpd['ro'] and vpd['ro'][field]) - for field in RW_FIELDS_SET1: - self.assertTrue(field in vpd['rw'] and vpd['rw'][field]) - self.assertEqual(vpd['ro']['keyboard_layout'], 'xkb:us::eng') - self.assertEqual(vpd['ro']['initial_locale'], 'en-US') - self.assertEqual(vpd['ro']['initial_timezone'], 'America/Los_Angeles') - self.assertEqual(vpd['rw']['wifi_mac'], '0b:ad:f0:0d:15:05') - self.assertEqual(vpd['rw']['cellular_mac'], '70:75:65:6c:6c:65') - - vpd = self.proxy.GetVPD('CR001016') - for field in RO_FIELDS: - self.assertTrue(field in vpd['ro'] and vpd['ro'][field]) - for field in RW_FIELDS_SET2: - self.assertTrue(field in vpd['rw'] and vpd['rw'][field]) - self.assertEqual(vpd['ro']['keyboard_layout'], 'xkb:us:intl:eng') - self.assertEqual(vpd['ro']['initial_locale'], 'nl') - self.assertEqual(vpd['ro']['initial_timezone'], 'Europe/Amsterdam') - self.assertEqual(vpd['rw']['wifi_mac'], '0b:ad:f0:0d:15:10') - self.assertEqual(vpd['rw']['cellular_mac'], '') - - # Checks MAC addresses - for i in range(25): - serial = 'CR0010%02d' % (i + 1) - vpd = self.proxy.GetVPD(serial) - wifi_mac = vpd['rw']['wifi_mac'] - self.assertEqual(wifi_mac, "0b:ad:f0:0d:15:%02x" % (i + 1)) - if i < 5: - cellular_mac = vpd['rw']['cellular_mac'] - self.assertEqual(cellular_mac, "70:75:65:6c:6c:%02x" % (i + 0x61)) - - # Checks invalid serial numbers - self.assertRaises(xmlrpclib.Fault, self.proxy.GetVPD, 'MAGICA') - return True - - def testUploadReport(self): - # Upload simple blob - blob = 'Simple Blob' - report_name = 'simple_blob.rpt' - report_path = os.path.join(self.data_dir, shopfloor.REPORTS_DIR, - report_name) - self.proxy.UploadReport('CR001020', shopfloor.Binary('Simple Blob'), - report_name) - self.assertTrue(os.path.exists(report_path)) - self.assertTrue(open(report_path).read(), blob) - - # Try to upload to invalid serial number - self.assertRaises(xmlrpclib.Fault, self.proxy.UploadReport, 'CR00200', blob) - - def testFinalize(self): - self.proxy.Finalize('CR001024') - self.assertRaises(xmlrpclib.Fault, self.proxy.Finalize, '0999') - - def testGetTestMd5sum(self): - md5_work = os.path.join(self.data_dir, shopfloor.UPDATE_DIR, - 'factory', 'latest.md5sum') - with open(md5_work, "w") as f: - f.write('0891a16c456fcc322b656d5f91fbf060') - self.assertEqual(self.proxy.GetTestMd5sum(), - '0891a16c456fcc322b656d5f91fbf060') - os.remove(md5_work) - - def testGetTestMd5sumWithoutMd5sumFile(self): - self.assertTrue(self.proxy.GetTestMd5sum() is None) - - def testGetRegistrationCodeMap(self): - self.assertEquals( - {'user': ('000000000000000000000000000000000000' - '0000000000000000000000000000190a55ad'), - 'group': ('010101010101010101010101010101010101' - '010101010101010101010101010162319fcc')}, - self.proxy.GetRegistrationCodeMap('CR001001')) - - # Make sure it was logged. - log = open(self.registration_code_log).read() - self.assertTrue(re.match( - '^MAGICA MADOKA A-A 1214,' - '000000000000000000000000000000000000' - '0000000000000000000000000000190a55ad,' - '010101010101010101010101010101010101' - '010101010101010101010101010162319fcc,' - '\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\n', log), repr(log)) - - def testUploadEvent(self): - # Check if events dir is created. - events_dir = os.path.join(self.data_dir, shopfloor.EVENTS_DIR) - self.assertTrue(os.path.isdir(events_dir)) - - # A new event file should be created. - self.assertTrue(self.proxy.UploadEvent('LOG_C835C718', - 'PREAMBLE\n---\nEVENT_1\n')) - event_file = os.path.join(events_dir, 'LOG_C835C718') - self.assertTrue(os.path.isfile(event_file)) - - # Additional events should be appended to existing event files. - self.assertTrue(self.proxy.UploadEvent('LOG_C835C718', - '---\nEVENT_2\n')) - with open(event_file, 'r') as f: - events = [event.strip() for event in f.read().split('---')] - self.assertEqual(events[0], 'PREAMBLE') - self.assertEqual(events[1], 'EVENT_1') - self.assertEqual(events[2], 'EVENT_2') - - -if __name__ == '__main__': - unittest.main() 
diff --git a/factory_setup/testdata/shopfloor/devices.csv b/factory_setup/testdata/shopfloor/devices.csv deleted file mode 100644 index 11b1083..0000000 --- a/factory_setup/testdata/shopfloor/devices.csv +++ /dev/null 
@@ -1,27 +0,0 @@ -serial_number,hwid,ro_vpd_serial_number,ro_vpd_keyboard_layout,ro_vpd_initial_locale,ro_vpd_initial_timezone,rw_vpd_wifi_mac,rw_vpd_cellular_mac,registration_code_user,registration_code_group -CR001001,MAGICA MADOKA A-A 1214,CR001001,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:01,70:75:65:6c:6c:61,0000000000000000000000000000000000000000000000000000000000000000190a55ad,010101010101010101010101010101010101010101010101010101010101010162319fcc -CR001002,MAGICA MADOKA A-A 1214,CR001002,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:02,70:75:65:6c:6c:62,0202020202020202020202020202020202020202020202020202020202020202ef7dc16f,030303030303030303030303030303030303030303030303030303030303030394460b0e -CR001003,MAGICA MADOKA A-A 1214,CR001003,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:03,70:75:65:6c:6c:63,04040404040404040404040404040404040404040404040404040404040404042e947a68,050505050505050505050505050505050505050505050505050505050505050555afb009 -CR001004,MAGICA MADOKA A-A 1214,CR001004,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:04,70:75:65:6c:6c:64,0606060606060606060606060606060606060606060606060606060606060606d8e3eeaa,0707070707070707070707070707070707070707070707070707070707070707a3d824cb -CR001005,MAGICA MADOKA A-A 1214,CR001005,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:05,70:75:65:6c:6c:65,080808080808080808080808080808080808080808080808080808080808080876360a27,09090909090909090909090909090909090909090909090909090909090909090d0dc046 -CR001006,MAGICA HOMURA A-A 7056,CR001006,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:06,0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a80419ee5,0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0bfb7a5484 -CR001007,MAGICA HOMURA A-A 7056,CR001007,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:07,,0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c41a825e2,0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d3a93ef83 -CR001008,MAGICA HOMURA B-A 8265,CR001008,xkb:de::ger,de-DE,Europe/Amsterdam,0b:ad:f0:0d:15:08,,0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0eb7dfb120,0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0fcce47b41 -CR001009,MAGICA HOMURA B-A 8265,CR001009,xkb:de::ger,de-DE,Europe/Amsterdam,0b:ad:f0:0d:15:09,,1010101010101010101010101010101010101010101010101010101010101010c772eab9,1111111111111111111111111111111111111111111111111111111111111111bc4920d8 -CR001010,MAGICA HOMURA C-A 2974,CR001010,xkb:fr::fra,fr-FR,Europe/Amsterdam,0b:ad:f0:0d:15:0a,,121212121212121212121212121212121212121212121212121212121212121231057e7b,13131313131313131313131313131313131313131313131313131313131313134a3eb41a -CR001011,MAGICA SAYAKA A-A 2258,CR001011,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0b,,1414141414141414141414141414141414141414141414141414141414141414f0ecc57c,15151515151515151515151515151515151515151515151515151515151515158bd70f1d -CR001012,MAGICA SAYAKA A-B 3304,CR001012,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0c,,1616161616161616161616161616161616161616161616161616161616161616069b51be,17171717171717171717171717171717171717171717171717171717171717177da09bdf -CR001013,MAGICA SAYAKA A-C 2142,CR001013,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0d,,1818181818181818181818181818181818181818181818181818181818181818a84eb533,1919191919191919191919191919191919191919191919191919191919191919d3757f52 -CR001014,MAGICA SAYAKA A-D 3565,CR001014,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0e,,1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a5e3921f1,1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b2502eb90 -CR001015,MAGICA SAYAKA A-E 1787,CR001015,xkb:us::eng,en-US,America/Los_Angeles,0b:ad:f0:0d:15:0f,,1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c9fd09af6,1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1de4eb5097 -CR001016,MAGICA MAMI A-A 2443,CR001016,xkb:us:intl:eng,nl,Europe/Amsterdam,0b:ad:f0:0d:15:10,,1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e69a70e34,1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f129cc455 -CR001017,MAGICA MAMI A-A 2443,CR001017,xkb:us:intl:eng,nl,Europe/Amsterdam,0b:ad:f0:0d:15:11,,20202020202020202020202020202020202020202020202020202020202020207e8a2dc4,212121212121212121212121212121212121212121212121212121212121212105b1e7a5 -CR001018,MAGICA MAMI A-A 2443,CR001018,xkb:us:intl:eng,zh-TW,Asia/Taipei,0b:ad:f0:0d:15:12,,222222222222222222222222222222222222222222222222222222222222222288fdb906,2323232323232323232323232323232323232323232323232323232323232323f3c67367 -CR001019,MAGICA MAMI A-A 2443,CR001019,xkb:us:intl:eng,zh-TW,Asia/Taipei,0b:ad:f0:0d:15:13,,242424242424242424242424242424242424242424242424242424242424242449140201,2525252525252525252525252525252525252525252525252525252525252525322fc860 -CR001020,MAGICA MAMI A-A 2443,CR001020,xkb:us:intl:eng,zh-TW,Asia/Taipei,0b:ad:f0:0d:15:14,,2626262626262626262626262626262626262626262626262626262626262626bf6396c3,2727272727272727272727272727272727272727272727272727272727272727c4585ca2 -CR001021,MAGICA KYOKO AA-A 0136,CR001021,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:15,,282828282828282828282828282828282828282828282828282828282828282811b6724e,29292929292929292929292929292929292929292929292929292929292929296a8db82f -CR001022,MAGICA KYOKO AA-A 0136,CR001022,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:16,,2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2ae7c1e68c,2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b9cfa2ced -CR001023,MAGICA KYOKO AB-A 9345,CR001023,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:17,,2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c26285d8b,2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d5d1397ea -CR001024,MAGICA KYOKO AC-A 3910,CR001024,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:18,,2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2ed05fc949,2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2fab640328 -CR001025,MAGICA KYOKO AD-A 3779,CR001025,xkb:jp::jpn,ja,Asia/Tokyo,0b:ad:f0:0d:15:19,,3030303030303030303030303030303030303030303030303030303030303030a0f292d0,3131313131313131313131313131313131313131313131313131313131313131dbc958b1 -link,LINK PROTO A-A 0569,CR000102,xkb:us::eng,en-US,America/Los_Angeles,01:23:45:67:89:ab,12:34:56:78:9a:bc,323232323232323232323232323232323232323232323232323232323232323256850612,33333333333333333333333333333333333333333333333333333333333333332dbecc73 
diff --git a/factory_setup/testdata/shopfloor/factory.tar.bz2 b/factory_setup/testdata/shopfloor/factory.tar.bz2 deleted file mode 100644 index fc1303f..0000000 --- a/factory_setup/testdata/shopfloor/factory.tar.bz2 +++ /dev/null Binary files differ 
diff --git a/factory_setup/testdata/shopfloor/latest.md5sum b/factory_setup/testdata/shopfloor/latest.md5sum deleted file mode 100644 index 65fd6ce..0000000 --- a/factory_setup/testdata/shopfloor/latest.md5sum +++ /dev/null 
@@ -1 +0,0 @@ -0891a16c456fcc322b656d5f91fbf060